package com.mini.framework.ioc;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import com.mini.framework.ioc.annotation.Component;
import com.mini.framework.ioc.annotation.Controller;
import com.mini.framework.ioc.annotation.Inject;
import com.mini.framework.ioc.reader.AnnotationReader;


public abstract class  AbstractApplicationContext implements ApplicationContext {
	
	protected Map<String,Object> map;
	
	private List<Object> list;
	
	protected BeanReader reader;

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getBean(String id) {
		return (T) map.get(id);
	}
	
	public abstract void addObjectToContext(Class<?> c,Object o);
	
	/**
	 * 加载.
	 * 
	 * @return
	 */
	public ApplicationContext load() {
		map = new ConcurrentHashMap<String,Object>();
		list = new CopyOnWriteArrayList<Object>();
		reader = AnnotationReader.getInstance();
		Set<Class<?>> classes = reader.getClasses();
		init(classes);
		try{
			inject();
		}catch(Exception e){
			e.printStackTrace();
		}
		
		return this;
	}
	
	/**
	 * 初始化.
	 * 
	 * @param clazz
	 */
	private void init(Set<Class<?>> clazz){
		
		for (Class<?> c : clazz) {
			try{
				Object o = c.newInstance();
				list.add(o);
				// 增加声明的组件到 ApplicationContext
				Set<String> keys = getInstanceMapKeys(c);
				for (String k : keys) {
					map.put(k, o);
				}
				
				// 增加其他对象到Context
				addObjectToContext(c, o);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 注入.
	 * 
	 */
	private void inject() throws Exception{
		for(Object obj:list){
			/****************Field 注入********************/
			Field[] fields = obj.getClass().getDeclaredFields();
			List<Field> fieldInjects = getInjectField(fields);
			for(Field field:fieldInjects){
				field.setAccessible(true);
				Class<?> clazz = field.getType();
				String key = field.getAnnotation(Inject.class).value();
				Object instance = map.get(key.length() > 0 ? key : clazz.getName());
				
				field.set(obj, instance);
			}
			
			/*****************Method 注入****************/
			Method[] methods = obj.getClass().getDeclaredMethods();
			List<Method> methodList = getInjectMethod(methods);
			for (Method method : methodList) {
				method.setAccessible(true);
				Class<?>[] params = method.getParameterTypes();
				Object[] p = new Object[params.length];
				for (int i = 0; i < p.length; i++) {
					Object instance = map.get(params[i].getName());
					if (instance != null) {
						p[i] = instance;
					}
				}
				method.invoke(obj, p);
			}
		}
	}
	
	private List<Method> getInjectMethod(Method[] methods){
		List<Method> list = new ArrayList<Method>();
		for(Method method:methods){
			if(method.getAnnotation(Inject.class) != null){
				list.add(method);
			}
		}
		return list;
	}
	
	/**
	 * 找出组件的域里面包含Inject注释的域
	 *
	 * @param fields
	 * @return
	 */
	private List<Field> getInjectField(Field[] fields){
		List<Field> list = new ArrayList<Field>();
		for(Field field: fields){
			if(field.getAnnotation(Inject.class) != null){
				list.add(field);
			}
		}
		return list;
	}
	
	/**
	 * 获取声明组件的所有访问key
	 *
	 * @param c
	 * @return
	 */
	private Set<String> getInstanceMapKeys(Class<?> c) {
		Set<String> ret = new LinkedHashSet<String>();
		
		ret.add(c.getName());
		
		int index = c.getName().lastIndexOf(".");
		String subName = c.getName().substring(index+1);
		String name = subName.replace(subName.charAt(0), (char)(subName.charAt(0)+32));
		
		ret.add(name);

		
		Class<?>[] interfaces = c.getInterfaces();
		for (Class<?> i : interfaces) {
			ret.add(i.getName());
		}

		Controller controller = c.getAnnotation(Controller.class);
		if(controller != null && controller.value().length() >0)
			ret.add(controller.value());
		
		// 把annotation的值作为key
		Component component = c.getAnnotation(Component.class);
		if (component != null && component.value().length() > 0)
			ret.add(component.value());
		
		return ret;
	}
	

}
